#include "rtsp.h"

/**************************************************************************
作者：王若语

日期：2022/4/6

功能描述：构造函数和析构函数

参数：文件名

**************************************************************************/
Rtsp::Rtsp(char * FileName){
	memcpy(mH264FileName,FileName,strlen(FileName));
	mRtp = new Rtp();
}

Rtsp::~Rtsp(){
	if(mRtp){
		delete mRtp;
	}
}

/**************************************************************************
作者：王若语

日期：2022/4/5

功能描述：创建socket连接（TCP 或 UDP，取决于参数）

参数：tcpOrUdp--用于判断创建TCP连接还是UDP连接,0即为TCP，1即为UDP

返回值sockfd：成功返回0，失败则返回-1

**************************************************************************/
int Rtsp::createSocket(int tcpOrUdp)
{
    int sockfd;
    int optval = 1;
    if (tcpOrUdp)
    {
        sockfd = socket(AF_INET, SOCK_DGRAM, 0);//创建UDP连接
        if(sockfd < 0){
            printf("创建UDP连接失败\n");
            return -1;
        }
    }
    else
    {
        sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建TCP连接
        if (sockfd < 0)
        {
            printf("创建TCP连接失败\n");
            return -1;
        }
    }
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&optval, sizeof(optval));//设置socket状态，允许在 bind （）过程中本地地址可重复使用
    return sockfd;
}

/**************************************************************************
作者：王若语

日期：2022/4/5

功能描述：绑定socket连接（绑定哪个socket取决于参数sockfd）

参数：sockfd--socket标识
     ip--ip地址
     port--端口号

返回值：成功返回0，失败则返回-1

**************************************************************************/
int Rtsp::bindSocketAddr(int sockfd, const char* ip, int port)
{
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip);
    if (bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) < 0)
    {
        printf("绑定失败\n");
        return -1;
    }
    return 0;
}

/**************************************************************************
作者：王若语

日期：2022/4/5

功能描述：接收客户端socket连接

参数：sockfd--监听的socket描述字
     ip--客户端ip地址
     port--客户端的端口号

返回值：clientfd--接受的客户端socket描述字

**************************************************************************/
int Rtsp::acceptClient(int sockfd, char* ip, int* port)
{
    int clientfd;
    socklen_t len = 0;
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    len = sizeof(addr);
    clientfd = accept(sockfd, (struct sockaddr *)&addr, &len);
    if (clientfd < 0)
    {
        printf("接收客户端失败\n");
        return -1;
    }
    //用于赋值地址和端口号
    strcpy(ip, inet_ntoa(addr.sin_addr));
    *port = ntohs(addr.sin_port);
    return clientfd;
}

//---------------------------解析请求消息---------------------------

/**************************************************************************
作者：王若语

日期：2022/4/5

功能描述：得到参数buf中的第一行，值为line，便于解析请求消息

参数：buf--一段字符串
     line--buf中的第一行

返回值：buf--删除原buf后剩下的那段字符串

**************************************************************************/
char* Rtsp::getLineFromBuf(char* buf, char* line)
{
    //若buf不为换行符，则line截取该buf指向的字符，且line与buf均向后移动一位
    while (*buf != '\n')
    {
        *line = *buf;
        line++;
        buf++;
    }
    //截取line为一行字符串
    *line = '\n';
    line++;
    *line = '\0';
    //buf再向后移动一位
    buf++;
    return buf;
}

/**************************************************************************
作者：王若语

日期：2022/4/5

功能描述：用于处理OPTIONS请求消息

参数：result--用于输出回应消息
     cseq--序列号

**************************************************************************/
int Rtsp::handleOptions(char* result, int cseq)
{
    sprintf(result, "RTSP/1.0 200 OK\r\n"
                    "CSeq: %d\r\n"
                    "Public: OPTIONS, DESCRIBE, SETUP, PLAY\r\n"
                    "\r\n",
                    cseq);
    return 0;
}

/**************************************************************************
作者：王若语

日期：2022/4/5

功能描述：用于处理DESCRIBE请求消息

参数：result--用于输出回应消息
     cseq--序列号
     url--网络url

**************************************************************************/
int Rtsp::handleDescribe(char* result, int cseq, char* url)
{
    char sdp[500];
    char ip[100];
    sscanf(url, "rtsp://%[^:]:", ip);
    //sdp格式
    sprintf(sdp, "v=0\r\n"
                 "o=- 9%ld 1 IN IP4 %s\r\n"
                 "t=0 0\r\n"
                 "a=control:*\r\n"
                 "m=video 0 RTP/AVP 96\r\n"
                 "a=rtpmap:96 H264/90000\r\n"
                 "a=control:track0\r\n",
                 time(NULL), ip);
    //输出回应消息
    sprintf(result, "RTSP/1.0 200 OK\r\nCSeq: %d\r\n"
                    "Content-Base: %s\r\n"
                    "Content-type: application/sdp\r\n"
                    "Content-length: %ld\r\n\r\n"
                    "%s",
                    cseq,
                    url,
                    strlen(sdp),
                    sdp);

    return 0;
}

/**************************************************************************
作者：王若语

日期：2022/4/5

功能描述：用于处理SETUP请求消息

参数：result--用于输出回应消息
     cseq--序列号
     clientRtpPort--客户端RTP端口号

**************************************************************************/
int Rtsp::handleSetup(char* result, int cseq, int clientRtpPort)
{
    sprintf(result, "RTSP/1.0 200 OK\r\n"
                    "CSeq: %d\r\n"
                    "Transport: RTP/AVP;unicast;client_port=%d-%d;server_port=%d-%d;ssrc=66668899;mode=play\r\n"
                    "Session: 4bbbd85c8e; timeout=600\r\n"
                    "\r\n",
                    cseq,
                    clientRtpPort,
                    clientRtpPort+1,
                    SERVER_RTP_PORT,
                    SERVER_RTCP_PORT);
    return 0;
}

/**************************************************************************
作者：王若语

日期：2022/4/5

功能描述：用于处理PLAY请求消息

参数：result--用于输出回应消息
     cseq--序列号

**************************************************************************/
int Rtsp::handlePlay(char* result, int cseq)
{
    sprintf(result, "RTSP/1.0 200 OK\r\n"
                    "CSeq: %d\r\n"
                    "Range: npt=0.000-\r\n"
                    "Session: 4bbbd85c8e; timeout=600\r\n\r\n",
                    cseq);
    return 0;
}

/**************************************************************************
作者：王若语

日期：2022/4/5

功能描述：用于处理PLAY请求消息

参数：result--用于输出回应消息
     cseq--序列号

**************************************************************************/
int Rtsp::handleTeardown(char* result, int cseq)
{
    sprintf(result, "RTSP/1.0 200 OK\r\n"
                    "CSeq: %d\r\n"
                    "\n\n",
                    cseq);
    return 0;
}

/**************************************************************************
作者：王若语

日期：2022/4/5

功能描述：用于处理客户端发来的请求消息

参数：clientSockfd--客户端socket描述字
     clientIP--客户端ip地址
     clientPort--客户端端口号
     serverRtpSockfd--服务器RTP连接描述字
     serverRtcpSockfd--服务器RTCP连接描述字

**************************************************************************/
void Rtsp::responseToClient(int clientSockfd, const char* clientIP, int clientPort, int serverRtpSockfd, int serverRtcpSockfd)
{
    char method[40];//用于储存消息类型
    char url[100];//用于储存网络url
    char version[40];//用于储存版本号
    int cseq;//用于储存序列号
    char *bufPtr;//用于解析各个值的中间变量
    char* receiveBuf = (char*)malloc(BUF_MAX_SIZE);//用于储存从客户端接收的请求消息
    char* sendBuf = (char*)malloc(BUF_MAX_SIZE);//用于储存从服务器发出的响应消息
    char line[400];//用于储存消息中的某一行
    while (1)
    {
        int receiveLen;
        //接收客户端数据，即请求消息
        receiveLen = recv(clientSockfd, receiveBuf, BUF_MAX_SIZE, 0);
        if (receiveLen <= 0)
        {
            printf("未接收到数据\n");
            goto end;
        }
        receiveBuf[receiveLen] = '\0';
        printf("---------------C->S--------------\n");
        printf("%s", receiveBuf);
        // 解析请求消息类型 
        bufPtr = getLineFromBuf(receiveBuf, line);
        if (sscanf(line, "%s %s %s\r\n", method, url, version) != 3)
        {
            printf("解析请求消息类型失败\n");
            goto end;
        }
        //解析序列号
        bufPtr = getLineFromBuf(bufPtr, line);
        if (sscanf(line, "CSeq: %d\r\n", &cseq) != 1)
        {
            printf("解析序列号失败\n");
            goto end;
        }
        //如果是SETUP，那么就再解析client_port
        if (!strcmp(method, "SETUP"))
        {
            while (1)
            {
                bufPtr = getLineFromBuf(bufPtr, line);
                if (!strncmp(line, "Transport:", strlen("Transport:")))
                {
                    //解析客户端使用的rtp和rtcp端口号，后面服务端就在在两个端口发送rtp和rtcp包数据。
                    sscanf(line, "Transport: RTP/AVP;unicast;client_port=%d-%d\r\n", &clientRtpPort, &clientRtcpPort);
                    break;
                }
            }
        }
        if (!strcmp(method, "OPTIONS"))
        {
            if (handleOptions(sendBuf, cseq))
            {
                printf("响应OPTIONS失败\n");
                goto end;
            }
        }
        else if (!strcmp(method, "DESCRIBE"))
        {
            if (handleDescribe(sendBuf, cseq, url))
            {
                printf("响应DESCRIBE失败\n");
                goto end;
            }
        }
        else if (!strcmp(method, "SETUP"))
        {
            if (handleSetup(sendBuf, cseq, clientRtpPort))
            {
                printf("响应SETUP失败\n");
                goto end;
            }
        }
        else if (!strcmp(method, "PLAY"))
        {
            if (handlePlay(sendBuf, cseq))
            {
                printf("响应PLAY失败\n");
                goto end;
            }
        }
        else if (!strcmp(method, "TEARDOWN"))
        {
            if (handleTeardown(sendBuf, cseq))
            {
                printf("响应TEARDOWN失败\n");
                goto end;
            }
        }
        else
        {
            goto end;
            printf("程序结束！！");
        }
        printf("---------------S->C--------------\n");
        printf("%s", sendBuf);
        send(clientSockfd, sendBuf, strlen(sendBuf), 0);
        //如果是PLAY请求，则开始发送RTP包
        if (!strcmp(method, "PLAY"))
        {
            //声明定义
            int frameSize, startCode, fd;
            extern int errno;
            char* frame = (char*)malloc(RTP_PACKET_SIZE);
            struct RtpPacket* rtpPacket = (struct RtpPacket*)malloc(RTP_PACKET_SIZE);
            if((fd = open(mH264FileName, O_RDONLY)) < 0)
            {
            	printf("打开文件失败，errno=%d\n",errno);
            	exit(1);
            }
            mRtp->rtpHeaderInit(rtpPacket, 0, 0, 0, RTP_VESION, RTP_PAYLOAD_TYPE_H264, 0, 0, 0, 0x66668899);
            printf("start play.....................................\n");
            printf("client ip:%s\n", clientIP);
            printf("client port:%d\n", clientRtpPort);
            //循环内不断发送
            while (1)
            {
                frameSize = mRtp->getOneFrameFromH264File(fd, frame, RTP_PACKET_SIZE);
                if (frameSize < 0)
                {
			printf("%s if(frameSize < 0) break \n",__func__);
			break;
                }

                if (mRtp->isStartCode3(frame))
                {
                    startCode = 3;
                }
                else
                {
                    startCode = 4;
                }
                frameSize -= startCode;
		// 把读取到的一帧数据frame 封装到rtp包发送到客户端在setup时指定的clientRtpPort。
                mRtp->rtpSendH264Frame(serverRtpSockfd, clientIP, clientRtpPort, rtpPacket, (uint8_t*)(frame + startCode), frameSize);
                rtpPacket->rtpHeader.timestamp += 90000/25;
				//控制播放帧率
                usleep(1000*1000/25);
            }
            //释放资源
            free(frame);
            free(rtpPacket);
            goto end;   
        }
    }
end:
    printf("finish\n");
    close(clientSockfd);//关闭连接
    //释放内存空间
    free(receiveBuf);
    free(sendBuf);
}

/**************************************************************************
作者：王若语

日期：2022/4/5

功能描述：运行RTSP的主函数

**************************************************************************/
int Rtsp::running(){
    int serverRtspSockfd;//定义服务器RTSP的TCP连接描述字
    serverRtspSockfd = createSocket(0);//建立TCP连接
    //printf("%d\n",SERVER_RTSP_PORT);
    if (serverRtspSockfd < 0)//如果失败
    {
		printf("建立TCP连接失败\n");
        return -1;
    }
    //绑定服务器RTSPsocket
    if (bindSocketAddr(serverRtspSockfd, "0.0.0.0", SERVER_RTSP_PORT) < 0)
    {
		printf("绑定服务器RTSPsocket失败\n");
        return -1;
    }
    //监听服务器RTSPsocket
    if (listen(serverRtspSockfd, 10) < 0)
    {
		printf("监听服务器RTSPsocket失败\n");
        return -1;
    }
    serverRtpSockfd = createSocket(1);//建立用于RTP协议的UDP
    serverRtcpSockfd = createSocket(1);//建立用于RTCP协议的UDP
    if (serverRtpSockfd < 0 || serverRtcpSockfd < 0)
    {
        printf("建立用于RTP或RTCP协议的UDP失败\n");
        return -1;
    }
    //绑定RTP和RTCP
    if (bindSocketAddr(serverRtpSockfd, "0.0.0.0", SERVER_RTP_PORT) < 0 || bindSocketAddr(serverRtcpSockfd, "0.0.0.0", SERVER_RTCP_PORT) < 0)
    {
        printf("绑定RTP或RTCP失败\n");
        return -1;
    }
    printf("rtsp://127.0.0.1:%d\n", SERVER_RTSP_PORT);
    while (1)
    {
        int clientSockfd;
        char clientIp[40];
        int clientPort;
        clientSockfd = acceptClient(serverRtspSockfd, clientIp, &clientPort);
        if (clientSockfd < 0)
        {
            printf("接受客户端失败\n");
            return -1;
        }
        printf("%s accept client;client ip:%s,client port:%d\n",__func__, clientIp, clientPort);
        responseToClient(clientSockfd, clientIp, clientPort, serverRtpSockfd, serverRtcpSockfd);
    }
}
